package org.groovymud.object.registry.persistence;

import groovy.lang.GroovyObjectSupport;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;

/**
 * loads an object using xstream, thread safe: cannot load and save at the same time
 * with any instance
 * @author corbym
 *
 * @param <T>
 */
public class XStreamDataSource<T> implements MudDataSource<T> {

	private List<Converter> converters;
	private String fileExtension = ".xml";
	private final String baseDirectory;

	@Autowired(required = true)
	XStream xStream;
	
	private boolean convertersRegistered = false;
	public static final Logger logger = Logger.getLogger(XStreamDataSource.class);

	public XStreamDataSource(String baseDir) {
		baseDirectory = baseDir;
	}

	public XStreamDataSource(String baseDir, String ext) {
		baseDirectory = baseDir;
		fileExtension = ext;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * This implementation loads the object from an xml document in the file system. The handle
	 * represents a directory under the specified base directory.
	 */
	public T loadObject(String fileName) throws DataSourceException {
		if (xStream == null) {
			throw new IllegalArgumentException("You must set the xstream instance before using this class.");
		}
		T object = null;
		File file = createFile(baseDirectory.trim() + File.separator + fileName);
		synchronized (this.getClass()) {
			if (file.exists()) {
				logger.info("loading player file");
				InputStream xmlIn;

				try {
					xmlIn = createFileInputStream(file);
				} catch (FileNotFoundException e) {
					throw new DataSourceException("file not found:" + file.getAbsolutePath(), e);
				}

				object = (T) deserializeObject(xmlIn);

			}
		}
		return object;
	}

	protected void registerConverters() {
		if (!convertersRegistered ) {
			for (Converter converter : converters) {
				getxStream().registerConverter(converter);
			}
			convertersRegistered = true;
		}

	}

	public void serialize(T object, OutputStream out) {
		registerConverters();
		xStream.toXML(object, out);
	}

	public T deserializeObject(InputStream xmlIn) {
		registerConverters();
		return (T) xStream.fromXML(xmlIn);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * This implementation saves the object as an xml document to the file system. The handle
	 * represents a directory under the specified base directory.
	 * 
	 */
	public void saveObject(String fileName, T object) throws DataSourceException {

		if (xStream == null) {
			throw new IllegalArgumentException("You must set the xstream instance before using this class.");
		}
		xStream.omitField(GroovyObjectSupport.class, "metaClass");
		xStream.setMode(XStream.XPATH_RELATIVE_REFERENCES);
		synchronized (this.getClass()) {
			FileOutputStream out;
			try {
				out = new FileOutputStream(createFile(baseDirectory.trim() + File.separator + fileName));
			} catch (FileNotFoundException e) {
				throw new DataSourceException("Cannot persist:" + fileName + " to " + baseDirectory, e);
			}
			serialize(object, out);
		}
	}

	public boolean deleteObject(String fileName) {
		File file = createFile(fileName);
		return file.delete();
	}

	protected InputStream createFileInputStream(File playerFile) throws FileNotFoundException {
		return new FileInputStream(playerFile);
	}

	protected File createFile(String fileName) {
		return new File(fileName + (fileName.endsWith(fileExtension) ? "" : fileExtension));
	}

	public XStream getxStream() {
		return xStream;
	}

	public void setxStream(XStream xStream1) {
		this.xStream = xStream1;
	}

	public String getBaseDirectory() {
		return baseDirectory;
	}

	/**
	 * @return the converters
	 */
	public List<Converter> getConverters() {
		return converters;
	}

	/**
	 * @param converters the converters to set
	 */
	public void setConverters(List<Converter> converters) {
		this.converters = converters;
	}
}
